/*
 * Copyright 2010 Kevin L'Huillier <klhuillier@gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.lhuillier.pwsafe.io.codec;


import java.util.List;
import java.util.UUID;
import org.lhuillier.pwsafe.io.RawDb;
import org.lhuillier.pwsafe.model.Database;
import org.lhuillier.pwsafe.model.RawField;
import org.lhuillier.pwsafe.model.Record;
import com.google.inject.Inject;


class RawDbDecoderImpl implements RawDbDecoder {
    private final BinConverter binConv;
    private final List<HeaderConverter> headerCodecs;
    private final List<FieldConverter> fieldCodecs;
    
    @Inject
    RawDbDecoderImpl(BinConverter binConv, List<HeaderConverter> headerCodecs,
            List<FieldConverter> fieldCodecs) {
        this.binConv = binConv;
        this.headerCodecs = headerCodecs;
        this.fieldCodecs = fieldCodecs;
    }

    @Override
    public Database decode(RawDb rawDb) {
        Database result = new Database(findUuid(rawDb.getHeaders()));
        decodeHeaders(result, rawDb);
        decodeRecords(result, rawDb);
        return result;
    }

    private void decodeHeaders(Database db, RawDb rawDb) {
        for (RawField field : rawDb.getHeaders()) {
            // Skip EOFs and UUIDs
            if (field.getTypeId() != -1 && field.getTypeId() != 1) {
                decodeHeader(db, field);
            }
        }
    }
    
    private void decodeHeader(Database db, RawField field) {
        byte typeId = field.getTypeId();

        for (HeaderConverter codec : headerCodecs) {
            if (codec.handledTypeId() == typeId) {
                codec.toDb(field, db);
                return;
            }
        }
        
        db.setUnknownField(typeId, field);
    }

    private void decodeRecords(Database db, RawDb rawDb) {
        for (RawRecord record : rawDb.getRecords()) {
            db.addRecord(decodeRecord(record));
        }
    }

    private Record decodeRecord(RawRecord record) {
        Record result = new Record(findUuid(record.getFields()));

        for (RawField field : record.getFields()) {
            byte typeId = field.getTypeId();
            
            if (typeId != -1 && typeId != 1) {
                decodeField(result, field);
            }
        }

        return result;
    }

    private void decodeField(Record record, RawField field) {
        for (FieldConverter codec : fieldCodecs) {
            if (codec.handledTypeId() == field.getTypeId()) {
                codec.toRecord(field, record);
                return;
            }
        }
        
        record.setUnknownField(field.getTypeId(), field);
    }
    
    private UUID readUuid(RawField field) {
        return binConv.readUuid(field.getValue());
    }

    /**
     * Looks for the UUID field in a list of fields. In both headers and
     * records, the type ID is 1. If no UUID is found, one is created.
     */
    private UUID findUuid(List<RawField> fields) {
        for (RawField field : fields) {
            if (field.getTypeId() == 1) {
                return readUuid(field);
            }
        }
        return UUID.randomUUID();
    }

}
